﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using System.Collections;
using System.Diagnostics;
using System.IO;
using Commons = NougakudoCompanion.Commons;

namespace NougakuDoController
{
    class Launcher
    {
        Dictionary<string, Process> runtimes;

        Commons.Runtime runtime;
        List<string> ErrorMsg;

        internal Launcher(Commons.Runtime runtime)
        {
            this.runtime = runtime;
            runtimes = new Dictionary<string, Process>();
        }

        /// <summary>
        /// Start all applications.
        /// modifi return value for exit code
        /// </summary>
        internal bool Run()
        {
            return Run(runtime.Applications.Keys.ToArray());
        }

        /// <summary>
        /// Start specific applications.
        /// modifi return value for exit code
        /// </summary>
        /// <param name="apps"></param>
        internal bool Run(string[] apps)
        {
            bool first = true;
            ErrorMsg = new List<string>();
            foreach (var app in apps)
            {
                if (runtime.Applications.ContainsKey(app) && !runtimes.ContainsKey(app))
                {
                    if (first)  // Wait 10 seconds for stabilization of http.sys's queue.
                    {
                        first = false;
                    }
                    else
                    {
                        System.Threading.Thread.Sleep(20000); 
                    }
                    Run(app);
                }
                else if (app == runtime.Environment.Name)
                {
                    Logging.Write("Launcher:Start not need = {0} for nougakudo runtime.", new object[] { app }, Logging.LogType.INFO);
                }
                else
                {
                    Logging.Write("Launcher:Start not start = {0} for running.", new object[] { app }, Logging.LogType.ERROR);
                }
            }
            WriteRunningLog();
            System.Threading.Thread.Sleep(Commons.Constants.STOP_INTERVAL);
            // add return code for exitcode.
            if (runtimes.Count == 0)
                return false;
            else
                return true;
        }

        /// <summary>
        /// Get the application name list of running.
        /// </summary>
        internal string[] RunningApplicationList
        {
            get
            {
                return runtimes.Keys.ToArray();
            }
        }

        /// <summary>
        /// Start single application.
        /// modifi return value for exit code
        /// </summary>
        /// <param name="app"></param>
        bool Run(string app)
        {
            Logging.Write("Launcher:Nougakudo Start {0}.", new object[] { app }, Logging.LogType.INFO);
            bool ret = GetCommandLines(app);
            if (!ret)
            {
                Logging.Write("Launcher:Nougakudo not started {0}.", new object[] { ErrorMsg.Last() }, Logging.LogType.ERROR);
                return false;
            }
            try
            {
                var exe = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, Commons.Constants.LAUNCHER);
                var procInfo = new ProcessStartInfo(exe);
                procInfo.WorkingDirectory = AppDomain.CurrentDomain.BaseDirectory;
                procInfo.Arguments = app + ".txt";
                var proc = Process.Start(procInfo);
                // add return code for exitcode.
                System.Threading.Thread.Sleep(3000);
                if (proc.HasExited && proc.ExitCode != 0)
                {
                    Logging.Write("Launcher:Ruby start error.", Logging.LogType.ERROR);
                    return false;
                }
                Logging.Write("Launcher:Nougakudo Started {0}.", new object[] { app }, Logging.LogType.INFO);
                runtimes[app] = proc;
            }
            catch (Exception e)
            {
                Logging.Write("Launcher:Nougakudo not started {0} : {1}", new object[] { app, e.Message }, Logging.LogType.ERROR);
            }
            return true;
        }

        bool GetCommandLines(string name)
        {
            try
            {
                var sourceFile = Path.Combine(runtime.Environment.ApplicationFolder, name, Commons.Constants.ENNOU_EXECUTABLE);
                var destFile = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, name + ".txt");
                File.Copy(sourceFile, destFile, true);
                return true;
            }
            catch (Exception e)
            {
                ErrorMsg.Add(e.Message);
                return false;
            }
        }

        /// <summary>
        /// Request stop to all running applications.
        /// </summary>
        /// <returns></returns>
        internal bool RequestStop()
        {
            return RequestStop(RunningApplicationList);
        }

        /// <summary>
        /// Request stop to specific applications.
        /// </summary>
        /// <param name="apps"></param>
        /// <returns></returns>
        internal bool RequestStop(string[] apps)
        {
            ErrorMsg = new List<string>();
            bool ret = false;
            foreach (var app in apps)
            {
                if (runtime.Applications.ContainsKey(app) && runtimes.ContainsKey(app))
                {
                    Logging.Write("Laucher:Stop request for {0}.", new object[] { app }, Logging.LogType.INFO);
                    ret = RequestStop(app);
                }
                else if (app == runtime.Environment.Name)
                {
                    Logging.Write("Launcher:Stop no need = {0} for nougakudo runtime.", new object[] { app }, Logging.LogType.INFO);
                }
                else
                {
                    Logging.Write("Launcher:Stop not running = {0} .", new object[] { app }, Logging.LogType.ERROR);
                }
            }
            WriteRunningLog();
            System.Threading.Thread.Sleep(Commons.Constants.STOP_INTERVAL);
            return ret;
        }

        /// <summary>
        /// Request stop to single application.
        /// </summary>
        /// <param name="app"></param>
        /// <returns></returns>
        bool RequestStop(string app)
        {
            var proc = runtimes[app];

            var ret = SendTerminate(app);
            proc.WaitForExit();
            runtimes.Remove(app);
            return true; 

        }

        /// <summary>
        /// send CTRL + C to single application.
        /// </summary>
        /// <param name="name"></param>
        /// <returns></returns>
        bool SendTerminate(string name)
        {
            try
            {
                var id = runtimes[name].Id.ToString();
                var file = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, id + ".txt");
                File.Delete(id);
                var st = File.Create(file);
                st.Close();
                return true;   
            }
            catch (Exception e)
            {
                ErrorMsg.Add(e.Message);
                return false;
            }
        }

        /// <summary>
        /// Stable NougakudoLanucher behavior for abnormal terminate of ruby.exe.
        /// </summary>
        internal void MaintenanceRunningProcess()
        {
            foreach (var name in RunningApplicationList)
            {
                if (runtimes[name].HasExited)
                {
                    Logging.Write("NougakudoLauncher:NougakuDo not started ({0} by {1}).", new object[] { name, runtimes[name].Id }, Logging.LogType.WARNING);
                    runtimes.Remove(name);
                    WriteRunningLog();
                }
            }
        }

        void WriteRunningLog()
        {
            var runs = runtimes.Keys;
            List<string> logs = new List<string>();
            foreach (var run in runs)
            {
                logs.Add(run + "\t" + runtimes[run].Id.ToString() + "\t" +
                         runtime.Applications[run].Version + "\t" +
                         runtime.Applications[run].NumOfProcess + "\t" +
                         runtime.Applications[run].Environment);
            }

            File.WriteAllLines(Commons.Constants.RUNNING_NOUGAKUDO, logs.ToArray());

            UploadRunningLog();
        }

        void UploadRunningLog()
        {
            var ret = NougakudoCompanion.Commons.BlobStorageHelper.UploadBlob(
                            runtime.Environment,
                            Commons.Constants.RUNNING_NOUGAKUDO + "-" + runtime.Environment.RoleInstanceID,
                            Path.GetFullPath(Commons.Constants.RUNNING_NOUGAKUDO));
            

            if (!ret.Item1)
            {
                Logging.Write("NougakudoController:Log upload error {0}.", new object[] { ret.Item2 }, Logging.LogType.ERROR);
            }
        }
    }
}
